﻿// ===============================================================================
// MappingGenerator.cs
// EOS Framework
// ===============================================================================
// Copyright (c) EOS Development Group. All rights reserved.
// =============================================================================

namespace EOS.Framework.Data.Generator
{
    using System.Collections.Generic;
    using System.Xml;
    using EOS.Framework.Tools.Aspects;

    public class MappingGenerator
    {
        private const string DefaultNamespace = "urn:nhibernate-mapping-2.2";
        private XmlDocument xmlDocument;
        private XmlElement classElement;

        [ParamNotNullOrEmpty("targetAssembly"), ParamNotNullOrEmpty("targetNamespace"), ParamNotEmpty("className"), ParamNotEmpty("tableName")]
        public MappingGenerator(string targetAssembly, string targetNamespace, string className, string tableName)
        {
            xmlDocument = new XmlDocument();

            XmlElement rootElement = xmlDocument.CreateElement("hibernate-mapping", DefaultNamespace);
            xmlDocument.AppendChild(rootElement);

            AddAttribute(rootElement, "namespace", targetNamespace);
            AddAttribute(rootElement, "assembly", targetAssembly);

            classElement = xmlDocument.CreateElement("class", DefaultNamespace);
            AddAttribute(classElement, "name", className);
            AddAttribute(classElement, "table", tableName);

            rootElement.AppendChild(classElement);
        }

        private void AddAttribute(XmlElement target, string name, string value)
        {
            XmlAttribute newAttribute = xmlDocument.CreateAttribute(name);
            newAttribute.Value = value;

            target.Attributes.Append(newAttribute);
        }

        [ParamNotNullOrEmpty("propertyName"), ParamNotNullOrEmpty("fieldName"), ParamNotNullOrEmpty("type")]
        public void AddDatabasePrimaryKey(string propertyName, string fieldName, string type, bool isIdentity)
        {
            XmlElement idElement = xmlDocument.CreateElement("id", DefaultNamespace);
            AddAttribute(idElement, "name", propertyName);
            AddAttribute(idElement, "column", fieldName);
            AddAttribute(idElement, "type", type);

            XmlElement generatorElement = xmlDocument.CreateElement("generator", DefaultNamespace);
            if (isIdentity)
            {
                AddAttribute(generatorElement, "class", "identity");
            }
            else
            {
                AddAttribute(generatorElement, "class", "assigned");
            }
            idElement.AppendChild(generatorElement);

            classElement.AppendChild(idElement);
        }

        [ParamNotNullOrEmpty("propertyName"), ParamNotNull("keys"), ParamNotNullOrEmpty("tableName"), ParamNotNullOrEmpty("className")]
        public void AddDatabaseOutReference(string propertyName, IEnumerable<string> keys, string tableName, string className)
        {
            AddDatabaseOutReference(propertyName, keys, tableName, className, null, false);
        }

        [ParamNotNullOrEmpty("propertyName"), ParamNotNull("keys"), ParamNotNullOrEmpty("tableName"), ParamNotNullOrEmpty("className")]
        public void AddDatabaseOutReference(string propertyName, IEnumerable<string> keys, string tableName, string className, string columName, bool isManyToMany)
        {
            XmlElement bagElement = xmlDocument.CreateElement("bag", DefaultNamespace);
            AddAttribute(bagElement, "name", propertyName);
            AddAttribute(bagElement, "table", tableName);

            if (!isManyToMany)
            {
                AddAttribute(bagElement, "inverse", true.ToString().ToLower());
                AddAttribute(bagElement, "lazy", true.ToString().ToLower());
                AddAttribute(bagElement, "cascade", "none");
            }

            foreach (string key in keys)
            {
                XmlElement keyElement = xmlDocument.CreateElement("key", DefaultNamespace);
                AddAttribute(keyElement, "column", key);

                bagElement.AppendChild(keyElement);
            }

            if (isManyToMany)
            {
                XmlElement manyToManyElement = xmlDocument.CreateElement("many-to-many", DefaultNamespace);
                AddAttribute(manyToManyElement, "class", className);
                AddAttribute(manyToManyElement, "column", columName);

                bagElement.AppendChild(manyToManyElement);
            }
            else
            {
                XmlElement oneToManyElement = xmlDocument.CreateElement("one-to-many", DefaultNamespace);
                AddAttribute(oneToManyElement, "class", className);

                bagElement.AppendChild(oneToManyElement);
            }


            classElement.AppendChild(bagElement);
        }

        [ParamNotNullOrEmpty("propertyName"), ParamNotNull("fieldName"), ParamNotNullOrEmpty("className")]
        public void AddDatabaseInReference(string propertyName, string fieldName, string className)
        {
            XmlElement manyToOneElement = xmlDocument.CreateElement("many-to-one", DefaultNamespace);
            AddAttribute(manyToOneElement, "name", propertyName);
            AddAttribute(manyToOneElement, "column", fieldName);
            AddAttribute(manyToOneElement, "class", className);
            AddAttribute(manyToOneElement, "cascade", "none");

            classElement.AppendChild(manyToOneElement);
        }

        [ParamNotNullOrEmpty("propertyName"), ParamNotNull("fieldName"), ParamNotNullOrEmpty("type")]
        public void AddDatabaseField(string propertyName, string fieldName, string type, int length, bool allowNull)
        {
            XmlElement propertyElement = xmlDocument.CreateElement("property", DefaultNamespace);
            AddAttribute(propertyElement, "name", propertyName);
            AddAttribute(propertyElement, "column", fieldName);
            AddAttribute(propertyElement, "type", type);
            AddAttribute(propertyElement, "not-null", (!allowNull).ToString().ToLower());

            if (length > 0)
            {
                AddAttribute(propertyElement, "length", length.ToString());
            }

            classElement.AppendChild(propertyElement);
        }

        [ParamNotNullOrEmpty("path")]
        public void SaveToFile(string path)
        {
            xmlDocument.Save(path);
        }
    }
}
